/**
 * Copyright (c) 2016-2020, Michael Yang 杨福海 (fuhai999@gmail.com).
 * <p>
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.rock.app.controller;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eova.common.utils.EncryptUtil;
import com.eova.model.Role;
import com.eova.model.User;
import com.jfinal.aop.Inject;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.weixin.sdk.api.ApiConfigKit;
import com.jfinal.weixin.sdk.api.ApiResult;
import com.jfinal.weixin.sdk.cache.IAccessTokenCache;
import com.jfinal.wxaapp.WxaConfig;
import com.jfinal.wxaapp.WxaConfigKit;
import com.jfinal.wxaapp.api.WxaUserApi;
import com.rock.app.base.OpenConsts;
import com.rock.app.base.OpenControllerBase;
import com.rock.app.base.WxaUserApiOpen;
import com.rock.sys.config.RockConst;
import com.rock.sys.model.TSApp;
import com.rock.sys.model.TSAppRelease;
import com.rock.sys.model.TSAppUser;
import com.rock.sys.model.TSUserOpenid;
import com.rock.sys.model.TUser;

import cn.hutool.core.util.StrUtil;

/**
 * 小程序相关的API
 */
public class WechatMpController extends OpenControllerBase {

    private Logger logger = LoggerFactory.getLogger(WechatMpController.class);
    
	@Inject
	private WxaUserApi wxaUserApi;

	@Inject
	private WxaUserApiOpen wxaUserApiOpen; // 微信第三方

	/**
	 * 1、小程序端调用 wx.login() 之后，会得到code ，
	 * 详情：https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html
	 * <p>
	 * 2、小程序端得到code之后，需调用此接口那code来换session_key，同时服务器存储session_key
	 * 3、为什么要存储session_key呢？目的是为了获取用户信息 4、小程序调用 wx.login() 之后，接下来会调用
	 * wx.getUserInfo() ， 此时要注意：wx.getUserInfo()得到的信息是加密的，需要解密才能获取文本信息
	 * <p>
	 * 5、解密就用到刚才的 session_key 了，session_key 其实就是解密的钥匙 （密钥）
	 */
	public void code2session() {

		String code = getPara("code");
		System.out.println("进入微信验证,code="+code);
		if (StrUtil.isBlank(code)) {
			renderFailJson(105, "code is blank");
			return;
		}

		ApiResult apiResult = null;
		TSAppRelease appr = this.getCurrentAppRelease();
		if (appr == null) {
			// 未找到终端信息，说明配置错误，appCode模式下不存在或存在多条release信息
			this.renderFailJson("小程序配置错误，无法自动登录");
			return;
			
		} else {
			// 已找到终端信息，使用终端信息访问api
			int type = appr.getDevType();
			if (type == TSAppRelease.DEV_TYPE_THIRD_AUTH) {
				// 第三方授权方式
				List<Record> rc = Db.find("select component_appid,component_access_token from t_s_addon_wechat_access_token");
				String componentAppid = rc.get(0).get("component_appid");
				String componentAccessToken = rc.get(0).get("component_access_token");
				apiResult = wxaUserApiOpen.getSessionKey(appr.getChannelAppId(), code, componentAppid, componentAccessToken);
			} else if (type == TSAppRelease.DEV_TYPE_DIRECT_SECRET) {
				// 密钥直联方式，暂时未用到
	            WxaConfig wxaConfig = new WxaConfig();
	            wxaConfig.setAppId(appr.getChannelAppId());
	            wxaConfig.setAppSecret(appr.getSecret());
	            wxaConfig.setMessageEncrypt(false); //采用明文模式，同时也支持混合模式
	            WxaConfigKit.putWxaConfig(wxaConfig);
	            
	            //当前线程的app设置为本app
	            WxaConfigKit.setThreadLocalAppId(wxaConfig.getAppId());
	            
	            if (WxaConfigKit.getWxaConfig() == null) {
					renderJson(Ret.fail("code", 106).set("message", "rock mini program config error"));
					return;
				} else {
		            //换取session，默认使用当前线程的app配置信息
		    		// 获取SessionKey 和 openId
		    		// 返回{"session_key":"nzoqhc3OnwHzeTxJs+inbQ==","expires_in":2592000,"openid":"oVBkZ0aYgDMDIywRdgPW8-joxXc4"}
					apiResult = wxaUserApi.getSessionKey(code);
				}
			}
		}

		if (!apiResult.isSucceed()) {
			renderFailJson(apiResult.getErrorCode(), apiResult.getErrorMsg());
			return;
		}

		String sessionKey = apiResult.getStr("session_key");
		String sessionId = StrUtil.uuid();

		// 把sessionKey存储起来，接下来用户解密要用到这个sessionKey
		IAccessTokenCache accessTokenCache = ApiConfigKit.getAccessTokenCache();
		accessTokenCache.set("wxa:session:" + sessionId, sessionKey);

		// 把sessionId传给客户端，客户端解密数据的时候，必须把这个sessionId传入进来，才能获取sessionKey
//        renderJson(Ret.ok().set("sessionId", sessionId));
		Map<String, Object> data = new HashMap<String, Object>();
		data.put("sessionId", sessionId);
		data.put("success", true);

		renderOkDataJson(data);

	}

	/**
	 * 小程序端调用 wx.getUserInfo() 后，得到的是加密的用户数据 需要调用此接口，才能获取到具体的数据 解密用户数据，小程序的相关接口
	 * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserInfo.html
	 * @throws Exception 
	 */
	public void decryptUserInfo() throws Exception {

		String postData = getRawData();
		if (StrUtil.isBlank(postData)) {
			renderFailJson(107, "can not get data");
			return;
		}

		JSONObject json = JSON.parseObject(postData);

		// 小程序端调用 /api/wechat/mp/code2session之后得到的sessionId
		String sessionId = json.getString("sessionId");

		IAccessTokenCache accessTokenCache = ApiConfigKit.getAccessTokenCache();
		String sessionKey = accessTokenCache.get("wxa:session:" + sessionId);
		if (StrUtil.isBlank(sessionKey)) {
			renderFailJson(107, "session id is error.");
			return;
		}

		// 不包括敏感信息的原始数据字符串，用于计算签名
		String rawData = json.getString("rawData");

		// 签名：使用 sha1( rawData + sessionkey ) 得到字符串，用于校验用户信息
		String signature = json.getString("signature");

		// 包括敏感数据在内的完整用户信息的加密数据
		// 具体加密方法在：https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html#%E5%8A%A0%E5%AF%86%E6%95%B0%E6%8D%AE%E8%A7%A3%E5%AF%86%E7%AE%97%E6%B3%95
		String encryptedData = json.getString("encryptedData");

		// 加密算法的初始向量
		String iv = json.getString("iv");

		// 用户信息校验
		boolean check = wxaUserApi.checkUserInfo(sessionKey, rawData, signature);
		if (check == false) {
			renderFailJson(500, "userInfo check fail");
			return;
		}

		// 服务端解密用户信息，得到原始的用户信息
		ApiResult apiResult = wxaUserApi.getUserInfo(sessionKey, encryptedData, iv);
		if (!apiResult.isSucceed()) {
			renderFailJson(apiResult.getErrorCode(), apiResult.getErrorMsg());
			return;
		}

		Integer userId = doGetOrCreateUser(apiResult);
		if (userId == null) {
			// 这种情况非常严重，一般情况下只有链接不上数据库了
			// 或者是在 RPC 下，无法调用到 provider 了
			renderFailJson(501, "can not query user or save user to database");
			return;
		}

		setJwtAttr(OpenConsts.JWT_USERID, userId);

		TUser loginUser = TUser.dao.findById(userId);

		// 设置 jwt Token 给客户端
		// 以后客户端通过此token定位用户信息
		
		//查询APP用户角色
		Record role = Db.findFirst("select r.id, r.name from t_s_app_role r , t_s_app_user u where u.rid=r.id and u.user_id=?", loginUser.getId());

		Map<String, Object> data = new HashMap<String, Object>();
		data.put("token", createJwtToken());
		data.put("nickname", loginUser.getNickname());
		data.put("avatar", loginUser.getAvatar());
		data.put("id", loginUser.getId());
		
		//设置角色信息
		if(role != null) {
			data.put("roleId", role.getStr("id"));
			data.put("roleName", role.getStr("name"));
		} else {
			data.put("roleId", "");
			data.put("roleName", "");
		}

		renderOkDataJson(data);
	}

	public Integer doGetOrCreateUser(ApiResult apiResult) {

		/**
		 * 文档：https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserInfo.html
		 * apiResult的数据格式如下 { "openId": "OPENID", "nickName": "NICKNAME", "gender":
		 * GENDER, "city": "CITY", "province": "PROVINCE", "country": "COUNTRY",
		 * "avatarUrl": "AVATARURL", "unionId": "UNIONID", "watermark": { "appid":
		 * "APPID", "timestamp": TIMESTAMP } }
		 */

		String openId = apiResult.get("openId");
		String unionId = apiResult.get("unionId");

		TUser user = null;

		TSApp app = getAttr(OpenConsts.ATTR_LOGINED_APP);

		// 优先根据 unioinId 进行查询
		if (StrUtil.isNotBlank(unionId)) {
			user = TUser.dao.findFirstByAppCodeAndUnionId(app.getAppCode(), unionId);
			if (user != null) {
				return user.getId();
			}
		}

		// 之后根据 openId 进行查询
		if (StrUtil.isNotBlank(openId)) {
			user = TUser.dao.findFirstByAppCodeAndOpenId(app.getAppCode(), openId);
			if (user != null) {
				return user.getId();
			}
		}

		// 都查询不到，说明该用户是一个新的用户，创建一个新的用户
		String nickName = apiResult.get("nickName");
		int gender = apiResult.getInt("gender");
		String city = apiResult.get("city");
		String province = apiResult.get("province");
		String country = apiResult.get("country");
		String avatarUrl = apiResult.get("avatarUrl");

		user = new TUser();
		user.setNickname(nickName);
		user.setInfo(country + province + city + "来源：" + TUser.SOURCE_WECHAT_MINIPROGRAM);
		user.setAvatar(avatarUrl);
		user.setRegTime(new Date());
		// 设置归属租户
		user.setTenantId(app.getTenantId());

		// 设置系统用户的角色，便于通过元数据统一控制数据访问权限 暂时不需要
//		logger.info("设置为系统默认的app用户角色 plat_tenant_app_user");
//		Integer rid = Db.use(OpenApiConsts.DB_SYS).queryInt("select id from eova_role where name=?",
//				"plat_tenant_app_user");
//		user.setRid(rid);
//		user.setRids(rid.toString());

//        user.setLogged(new Date());

		// 设置激活状态
//        boolean isNotActivate = JPressOptions.getAsBool("reg_users_is_not_activate");
//        if (isNotActivate) {
//            user.setStatus(User.STATUS_REG);
//        }else {
//            user.setStatus(User.STATUS_OK);
//        }

		user.save();
		Integer userId = user.getId();

		saveOrUpdate(userId, TSUserOpenid.TYPE_WECHAT, openId);
		saveOrUpdate(userId, TSUserOpenid.TYPE_WECHAT_UNIONID, unionId);

		// 创建APP与用户的关系
		TSAppUser appUser = new TSAppUser();
		appUser.setAppId(app.getId());
		appUser.setUserId(userId);
		appUser.setTenantId(app.getTenantId());
		appUser.save();

		// 创建系统用户，只是为了后续的登陆身份获取
      //设置系统用户的角色，便于通过元数据统一控制数据访问权限
        Role role = Role.dao.findFirst("select * from eova_role where name=?", RockConst.TENANT_APP_USER_ROLE);
        
		User lUser = new User();
		lUser.set("id", userId);
		lUser.set("login_id", userId + "_loginid");
		lUser.set("login_pwd", EncryptUtil.getSM32(userId + "_pwd"));
		lUser.set("rid", role.get("id"));
		lUser.set("rids", role.get("id"));
		lUser.save();

		return userId;
	}

	public boolean saveOrUpdate(Integer userId, String type, String openId) {


        if (StrUtil.isBlank(openId)){
            return false;
        }

        TSUserOpenid userOpenid = TSUserOpenid.dao.findByUserIdAndType(userId, type);
        if (userOpenid != null) {
            String oldOpenId = userOpenid.getValue();
            if (!openId.equals(oldOpenId)) {
                userOpenid.setValue(openId);
                userOpenid.update();
            }
            return true;
        }


        userOpenid = new TSUserOpenid();
        userOpenid.setValue(openId);
        userOpenid.setType(type);
        userOpenid.setUserId(userId.longValue());
        userOpenid.save();
        return true;
    
	}
}
